/*
---@desc 初始化
---@param data @ 参数字典 
---data.view:所依赖的ScrollView; 
---data.delegate:代理；
---data.cellNumSize:cell数量，cellSize.width、cellSize.height几行几列；
---data.cellSize:cell的大小
---data.isReuse:是否启用cell复用，如果不启用对应行列cell只创建一次(laziCreate), 可以对cell进行setName进行标记 默认为"def"
---data.isSchedule:是否进行分帧创建cell
---data.extraOffset:周边所留区域，单位为像素
---data.reloadDataSchedule:reloadData的时候是否需要分帧更新cell
---data.minScale 最小缩放
---data.maxScale 最大缩放
---data.isCreateAll 是否分帧创建所有
---代理方法为：
---cellAtIndex(view, row,col)：获取一个对应行列的cell,在cell创建的时候调用
---numSizeOfCells:返回此列表几行几列也可以通过data.cellNumSize参数进行传递
---cellWillHide(view,row,col,cell)：当cell超出边界被隐藏的时候调用
---cellWillShow(view,row,col,cell)：当cell被显示的时候调用
---onEventForCollectionView:处理ScrollView的事件需要实现这个方法(非必须，如果是对scrollview直接滑动操作的需要，否则不需要)
*/

import * as cc from "cc";

interface ViewParams {
    view: cc.ScrollView;
    delegate: any;
    cellNumSize?: cc.Size;
    cellSize: cc.Size;
    isReuse: boolean;
    isSchedule?: boolean;
    extraOffset?: number;
    minScale?: number;
    maxScale?: number;
    reloadDataSchedule?: boolean;
    moveTimeForPerCell?: number;
    isCreateAll?: boolean;
}

export class CollectionView {
    public _isCreating: boolean = false;
    public _createdCellMap: {} = {};
    public _waitCreateQueue: any[] = [];
    public _recycleQueue: {} = {};
    public _frameIndexInfo: number[] = [0, 0, 0, 0];
    public _isReload: boolean = false;
    public _isCreateAllOver: boolean = false;
    public _zoomIng: boolean = false;
    public _view: cc.ScrollView;
    public _delegate: any;
    public _cellNumSize: cc.Size = new cc.Size(0, 0);
    public _cellSize: cc.Size = new cc.Size(0, 0);
    public _viewSize: cc.Size = new cc.Size(0, 0);
    public _isReuse: boolean = false;
    public _isSchedule: boolean = false;
    public _extraOffset: number = 0;
    public _minScale: number = 0;
    public _maxScale: number = 2;
    public _moveTimeForPerCell: number = 0;
    public _reloadDataSchedule: boolean = false; // reloadData 时会否分帧加载
    public _isCreateAll: boolean = false;
    public _visibleRect: cc.Rect;
    public _innerContainerSize: cc.Size;
    public _scheduleTime: number = 0;
    public _contentUITrans: cc.UITransform;
    public _viewNodeUITrans: cc.UITransform;

    public _callback1: Function; // 1001
    public _callback2: Function; // 101

    public constructor(param: ViewParams) {
        this._view = param.view
        this._delegate = param.delegate
        this._cellSize = param.cellSize
        this._isReuse = param.isReuse
        this._isSchedule = param.isSchedule || false
        this._reloadDataSchedule = param.reloadDataSchedule
        this._isCreateAll = param.isCreateAll

        if (param.cellNumSize) {
            this._cellNumSize = param.cellNumSize
        }

        if (typeof param.extraOffset === "number") {
            this._extraOffset = param.extraOffset
        }

        if (typeof param.minScale === "number") {
            this._minScale = param.minScale
        }

        if (typeof param.maxScale === "number") {
            this._maxScale = param.maxScale
        }

        if (typeof param.moveTimeForPerCell === "number") {
            this._moveTimeForPerCell = param.moveTimeForPerCell
        }

        this._innerContainerSize = cc.size(this._cellSize.width * this._cellNumSize.width, this._cellSize.height * this._cellNumSize.height)
        this._contentUITrans = this._view.content.getComponent(cc.UITransform);
        this._viewNodeUITrans = this._view.node.getComponent(cc.UITransform)
        this._contentUITrans.setContentSize(this._innerContainerSize)
        this.resetViewSize();
        this._view.node.on("scrolling", this.onEventForCollectionView.bind(this))
    }

    /**
     * @desc 重新设置视窗大小
     */
    resetViewSize(){
        this._viewSize = this._viewNodeUITrans.contentSize;
        this._visibleRect = cc.rect(0, 0, this._viewSize.width, this._viewSize.height)
        this._scheduleTime = this._moveTimeForPerCell/(this._viewSize.width/this._cellSize.width + this._viewSize.height/this._cellSize.height + 2)*0.5
    }

    onEventForCollectionView(params:{[key:string]:any} = {}) {
        if (!this._isReload) {
            return
        }

        if (this._zoomIng) {
            return
        }

        if (this._delegate.onEventForCollectionView) {
            this._delegate.onEventForCollectionView()
        }

        let immediately = params.immediately
		this.checkUpdateCell(immediately)
    }

   /**
    * @desc 刷新视图
    * @param adjustPos 自动适配 content 的位置至左上角
    */
    reloadData(adjustPos:boolean = false){
        this._isCreating = false
        this._isCreateAllOver = false
        // --清除当前显示cell
        for (const key in this._createdCellMap) {
            let v = this._createdCellMap[Number(key)]
            if (v instanceof cc.Node) {
                v.destroy()
            }
        }
    
        for (const key in this._recycleQueue) {
            let v = this._recycleQueue[Number(key)]
            if (v) {
                for (let index = 0; index < v.length; index++) {
                    let cell = v[index];
                    if (cell instanceof cc.Node) {
                        cell.destroy()
                    }
                }
            }
        }
    
        this._recycleQueue = {}
        this._createdCellMap = {}
        this._waitCreateQueue = []
        this._frameIndexInfo = [0,0,0,0]
        this._view.unschedule(this._callback2) 
        if (this._delegate.numSizeOfCells) {
            this._cellNumSize = this._delegate.numSizeOfCells()
        }
        
        let container = this._view.content
        let pos = this._view.content.getPosition()
        let scale = container.scale
        let width = this._cellSize.width*this._cellNumSize.width
        let height = this._cellSize.height*this._cellNumSize.height
        this._innerContainerSize = cc.size(width, height)
        let curSize = cc.size(this._innerContainerSize.width * scale.x, this._innerContainerSize.height * scale.y) 

        this._contentUITrans.setContentSize(curSize)
        this._view.content.setPosition(pos)

        if (adjustPos) {
            let pos_ = new cc.Vec3(0, this._viewSize.height-this._innerContainerSize.height)
            this._view.content.setPosition(pos_)
        }

        this._isReload = true
        this.checkUpdateCell(!this._reloadDataSchedule)
    }

    /*     ---
    ---@desc 设置周边预留区域
    ---@param extraOffset @ 单位像素
    ---@return */
    setExtraOffset(extraOffset:number){
        this._extraOffset = extraOffset
    }

    /*     ---
    ---@desc 获取某行某列的cell
    ---@param row @ 行
    ---@param col @ 列
    ---@return */
    getCellByRowAndCol(row:number,col:number){
        let index = row*this._cellNumSize.width + col
        let tempCell = this._createdCellMap[index]
        return tempCell
    }

    /* ---
    ---@desc 如果需要复用的话需要通过此方法从缓冲池中获取cell
    ---@param key @ 可以是cell的name,默认"def"
    ---@return */
    dequeueCellByKey(key?):Node{
        key = key || "def"
        let queue:[] = this._recycleQueue[key]
        if (queue && queue.length > 0) {
            return queue.pop()
        }
        return null
    }

    // 获得当前cell数量
    getCountOfCell(){
        let count = 0
        for (const key in this._createdCellMap) {
            let v = this._createdCellMap[Number(key)];
            if (v === true) {
                count += 1
            }
        }
        return count
    }

    /* ---
    ---@desc 获取当前界面显示的cell
    ---@return */
    getCurrentCells(){
        let cells = []
        for (const key in this._createdCellMap) {
            let v = this._createdCellMap[Number(key)];
            if (v instanceof cc.Node) {
                cells.push(v)
            }
        }
        return cells
    }

    zoomScaleByTime(scale: number, time: number, center?: cc.Vec3){
        let container = this._view.content  
        center = center || this._viewNodeUITrans.convertToWorldSpaceAR(new cc.Vec3(this._viewSize.width*0.5, this._viewSize.height*0.5))
        if (scale == container.scale.x && scale == container.scale.y ){
            return
        }
        let curScale = container.scale.x
        let scaleOffset = scale - curScale
        let startTime = new Date() 
        if (this._callback1) {
            this._view.unschedule(this._callback1)
        }
        this._callback1 = function(){
            let timeOffset_ = new Date()
            let timeOffset = (timeOffset_.getTime() - startTime.getTime())/1000
            let timePercent = timeOffset/time
            timePercent = timePercent > 1 ? 1 : timePercent
            let targetScale = curScale + scaleOffset*timePercent
            this.setZoomScale(targetScale,center)

            if (timePercent >= 1) {
                this._view.unschedule(this._callback1)
            }
        }
        this._view.schedule(this._callback1, 0)
    }

    zoomScaleToCenterByTime(scale: number, time: number, center?: cc.Vec3, showCenter?: cc.Vec3,callfun?:Function,callfunTime?:number,endCallfun?:Function){
        center = center || this._viewNodeUITrans.convertToWorldSpaceAR(new cc.Vec3(this._viewSize.width*0.5, this._viewSize.height*0.5))
        showCenter = showCenter || center
        let container = this._view.content
        let curScale = container.scale
        let scaleOffset = 0
        if (scale && scale > 0){
            scaleOffset = scale - curScale.x
        }
        let showOffset = new cc.Vec2(showCenter.x - center.x, showCenter.y - center.y)

        let startTime = (new Date()).getTime()/1000
        let updateTime = startTime
        if (this._callback1) {
            this._view.unschedule(this._callback1)
        }
        this._zoomIng = true
        this._callback1 = function(){
            let timeNow = (new Date()).getTime()/1000
            let timeEnd = timeNow
            if (timeNow -  startTime > time) {
                timeEnd = startTime + time
            } 
            let timeOffset = timeEnd - updateTime
            updateTime = timeEnd
            let timePercent = timeOffset / time

            if (callfunTime && timeNow - startTime >= callfunTime) {
                if (callfun) {
                    callfun()
                }
                callfun = null
            }

            if (timeNow -  startTime >= time){
                this._view.unschedule(this._callback1)
                if (callfun) {
                    callfun()
                }
                callfun = null
                if (endCallfun) {
                    endCallfun()
                }
                endCallfun = null
            }
            
            let moveOffset = new cc.Vec2(showOffset.x * timePercent,showOffset.y * timePercent)
            
            let targetPos = this._view.content.node.position
            let curSize
            if (scaleOffset != 0) {
                let scale = curScale.x + scaleOffset*timePercent
                let realScale = Math.max(this._minScale, Math.min(this._maxScale, scale)) 
                curScale.set(scale,scale,scale) 
                
                curSize = cc.size(this._innerContainerSize.width * realScale, this._innerContainerSize.height * realScale) 
        
                if (curSize.height < this._viewSize.height || curSize.width < this._viewSize.width) {
                    return
                }
                this._zoomIng = true
                let oldCenter = this._contentUITrans.convertToNodeSpaceAR(center)
                container.setScale(realScale,realScale,realScale)
                this._view.content.size = curSize
                let newCenter = this._contentUITrans.convertToWorldSpaceAR(oldCenter)
                let offset = center.subtract(newCenter)
                let newOffset = moveOffset.add(cc.v2(offset.x,offset.y))
                center = center.add(cc.v3(moveOffset.x,moveOffset.y))
                targetPos = this._view.content.position.add(newOffset) 
            }else{
                this._zoomIng = true
                curSize = cc.size(this._innerContainerSize.width, this._innerContainerSize.height)
                center = center.add(cc.v3(moveOffset.x,moveOffset.y)) 
                targetPos = targetPos.add(moveOffset) 
            }

            targetPos.x = targetPos.x > 0 ? 0 : targetPos.x
            targetPos.y = targetPos.y > 0 ? 0 : targetPos.y
            let minX = this._viewSize.width - curSize.width 
            let minY = this._viewSize.height - curSize.height 
            targetPos.x = targetPos.x < minX ? minX : targetPos.x
            targetPos.y = targetPos.y < minY ? minY : targetPos.y 
            this._view.content.position = targetPos
            this._zoomIng = false
            this.checkUpdateCell()
        }
        this._view.schedule(this._callback1, 0)
    }

    /* ---
    ---@desc 缩放方法
    ---@param scale @ 缩放倍数
    ---@param center @ 缩放中心点
    ---@return */
    setZoomScale(scale:number, center?){
        let container = this._view.content
        center = center || this._viewNodeUITrans.convertToWorldSpaceAR(new cc.Vec3(this._viewSize.width*0.5, this._viewSize.height*0.5))
        if (scale == container.scale.x && scale == container.scale.y){return}

        let realScale = Math.max(this._minScale, Math.min(this._maxScale, scale)) 
        let curSize = cc.size(this._innerContainerSize.width * realScale, this._innerContainerSize.height * realScale) 
        
        if (curSize.height < this._viewSize.height || curSize.width < this._viewSize.width){
            return
        }
        this._zoomIng = true
        let oldCenter = this._contentUITrans.convertToNodeSpaceAR(center)
        container.setScale(realScale,realScale,realScale)
        this._contentUITrans.setContentSize(curSize.width,curSize.height)
        let newCenter = this._contentUITrans.convertToWorldSpaceAR(oldCenter)
        let offset = center.sub(newCenter)
        let targetPos = this._view.content.position.add(offset) 
        targetPos.x = targetPos.x > 0 ? 0 : targetPos.x
        targetPos.y = targetPos.y > 0 ? 0 : targetPos.y
        let minX = this._viewSize.width - curSize.width 
        let minY = this._viewSize.height - curSize.height 
        targetPos.x = targetPos.x < minX ? minX : targetPos.x
        targetPos.y = targetPos.y < minY ? minY : targetPos.y 
        this._view.content.position = targetPos
        this._zoomIng = false
        this.checkUpdateCell()
    }
    
    createCellByInfo(info:Array<number>, needCheck?:boolean){
        let index = info[0]*this._cellNumSize.width + info[1]
        let rowTemp = this._cellNumSize.height-1-info[0]
        let cell = this._delegate.cellAtIndex(this._view, rowTemp, info[1])
        cell.anchorX = 0
        cell.anchorY = 0
        cell.setPosition(new cc.Vec2(info[1]*this._cellSize.width,info[0]*this._cellSize.height))
        if (!cell.parent) {
            this._view.content.addChild(cell)
        }

        this._createdCellMap[index] = cell
        // cell.active = false
        this._setCellActive(cell,false);

        if (needCheck && 
            !(info[1] >= this._frameIndexInfo[0] && info[1] <= this._frameIndexInfo[1] 
                && info[0] >= this._frameIndexInfo[2] && info[0] <= this._frameIndexInfo[3])) {
            return
        }
        // cell.active = true
        this._setCellActive(cell,true);
        if (this._delegate.cellWillShow) { 
            this._delegate.cellWillShow(this._view, rowTemp,info[1], cell)
        }
    }

    createCellImmediate() {
        let stepFunc = ()=>{
            let info = this._waitCreateQueue.pop()
            if (!info) {
                this._isCreating = false
                return false
            }
            this.createCellByInfo(info,true)
            return true
        }

        while(stepFunc());
    }

    createCellByQueue(){
        if (this._waitCreateQueue.length > 0 && !this._isCreating) {
            this._isCreating = true

            if (this._callback2) {
                this._view.unschedule(this._callback2)
            }
            let startTime = new Date()
            let prevTime = startTime.getTime()/1000
            let self = this
            this._callback2 = function(){
                let stepFunc = function(){
                    let info = self._waitCreateQueue.pop()
                    if (!info) {
                        self._view.unschedule(self._callback2)
                        self._isCreating = false
                        return false
                    }
                    self.createCellByInfo(info,true)
                    return true
                }

                let startTime = new Date()
                while (stepFunc() && (startTime.getTime() - prevTime) < 1/15) {
                }
                prevTime = startTime.getTime()/1000
            }
            this._view.schedule(this._callback2, this._scheduleTime || 0)
        } 
    }

    /*     
    ---@desc 刷新当前界面显示的cell
    ---@param immediately @ 是否立即，否则分帧
    ---@return */
    reFreshCurCells(immediately?:boolean){
        for (const i in this._createdCellMap) {
            if (this._createdCellMap.hasOwnProperty(Number(i))) {
                let v = this._createdCellMap[Number(i)];
                let row = Math.floor(Number(i)/this._cellNumSize.width)
                let col = Number(i)%this._cellNumSize.width
                if (v instanceof cc.Node) {
                    // if (v.activeInHierarchy) {
                    //     v.active = false
                    if (this._isCellActive(v)) {
                        this._setCellActive(v, false);

                        if (this._delegate.cellWillHide) {
                            this._delegate.cellWillHide(this._view, row, col, v)
                        }
                        // --如果需要复用则加入队列
                        if (this._isReuse) {
                            let key = v["cellType"]
                            if (!key) {
                                key = "def" 
                            }
                            let queue = this._recycleQueue[key] || {}
                            queue.push(v)
                            this._createdCellMap[Number(i)] = null
                            this._recycleQueue[key] = queue
                        }
                    }
                }
            }
        }
        this.checkUpdateCell(immediately)
    }

    /*
    ---
    ---@desc 检测更新显示区cell
    ---@param immediately @ 立即刷新显示区cell不进行分帧处理
    ---@return
    */
    checkUpdateCell(immediately?:boolean) {
        if (! this._isReload) {
            return
        }

        let container = this._view.content
        let scale = container.getScale(new cc.Vec3())
        let pos = this._view.content.position

        this._visibleRect.x = -pos.x < 0 ? 0 : -pos.x
        this._visibleRect.y = -pos.y < 0 ? 0 : -pos.y

        this._visibleRect.x /= scale.x
        this._visibleRect.y /= scale.y
        this._visibleRect.height = this._viewSize.height/scale.y
        this._visibleRect.width = this._viewSize.width/scale.x
        this._visibleRect.x = Math.min(this._visibleRect.x,this._innerContainerSize.width - this._viewSize.width/scale.x)
        this._visibleRect.y = Math.min(this._visibleRect.y,this._innerContainerSize.height - this._viewSize.height/scale.y)
        
        let top = this._visibleRect.y + this._visibleRect.height
        let bottom = this._visibleRect.y
        let left = this._visibleRect.x
        let right = this._visibleRect.x + this._visibleRect.width

        if (this._extraOffset) {
            if (top + this._extraOffset <= this._innerContainerSize.height) {
                top += this._extraOffset
            }
            if (bottom - this._extraOffset >= 0) {
                bottom -= this._extraOffset
            }
            if (left - this._extraOffset >= 0) {
                left -= this._extraOffset
            }
            if (right + this._extraOffset <= this._innerContainerSize.width) {
                right += this._extraOffset
            }
        }

        let fromXIndex = Math.floor(left / this._cellSize.width)
        let toXIndex = Math.ceil(right / this._cellSize.width)
        toXIndex -= 1

        let fromYIndex = Math.floor(bottom / this._cellSize.height)
        let toYIndex = Math.ceil(top / this._cellSize.height)
        toYIndex -= 1

        fromXIndex = Math.max(0, fromXIndex) // 
        fromYIndex = Math.max(0, fromYIndex)

        this._frameIndexInfo = [fromXIndex, toXIndex, fromYIndex, toYIndex]

        for (let i = fromXIndex; i < toXIndex+1; i++) {
            for (let j = fromYIndex; j < toYIndex+1; j++) {
                let row = j
                let rowTemp = this._cellNumSize.height-1-row
                let col = i
                let index = row * this._cellNumSize.width + col
                let tempCell = this._createdCellMap[index]
                if (!tempCell) {
                    if (this._isSchedule && !immediately) {
                        this._createdCellMap[index] = true
                        let array_ = [row, col]
                        this._waitCreateQueue.unshift(array_) // 头部插入
                    }else{
                        let cell = this._delegate.cellAtIndex(this._view, rowTemp, col)
                        cell.anchorX = 0
                        cell.anchorY = 0
                        // cell.active = true
                        this._setCellActive(cell,true);
                        if (this._delegate.cellWillShow) {
                            this._delegate.cellWillShow(this._view, rowTemp, col, cell)
                        }
                        cell.setPosition(new cc.Vec2(col*this._cellSize.width,row*this._cellSize.height))
                        if (!cell.parent) {
                            this._view.content.addChild(cell)
                        }

                        this._createdCellMap[index] = cell
                    }
                // }else if (tempCell != true && !tempCell.active) {
                //     tempCell.active = true
                }else if (tempCell != true && !this._isCellActive(tempCell)) {
                    this._setCellActive(tempCell, true);

                    if (this._delegate.cellWillShow) {
                        this._delegate.cellWillShow(this._view, rowTemp, col, tempCell)
                    }
                }
            }
        }

        // 如果中途调用立即加载,需要立即完成队列里面的任务，清空队列
        if (this._waitCreateQueue.length > 0 && immediately ){
            while (true) {
                let info = this._waitCreateQueue.pop()
                if (!info) {
                    if (this._callback2) {
                        this._view.unschedule(this._callback2)
                    }
                    this._isCreating = false
                    break
                }
                this.createCellByInfo(info)
            }
        }

        // --创建所有,除了显示区域外，分帧创建所有,和上面代码段顺序不能颠倒，防止被立即创建
        if (this._isCreateAll && !this._isCreateAllOver) {
            for (let i = 0; i < this._cellNumSize.height; i++) {
                for (let j = 0; j < this._cellNumSize.width; j++) {
                    let row = i
                    let col = j
                    let index = row*this._cellNumSize.width + col
                    if (!this._createdCellMap[index]) {
                        this._createdCellMap[index] = true
                        this._waitCreateQueue.push([i,j])
                    }
                }
            }
            this._isCreateAllOver = true
        }

        // --对队列中的进行加载
        if (immediately) {
            this.createCellImmediate();
        } else {
            this.createCellByQueue()
        }

        // --超出显示区域进行隐藏
        // -- let removeKeys = {}
        for (let i in this._createdCellMap) {
            let v = this._createdCellMap[Number(i)]
            let row = Math.floor(Number(i)/this._cellNumSize.width)
            let rowTemp = this._cellNumSize.height-1-row
            let col = Number(i) % this._cellNumSize.width
            let indexRect = cc.rect(fromXIndex, fromYIndex, toXIndex - fromXIndex, toYIndex - fromYIndex)
            
            if (!indexRect.contains(new cc.Vec2(col, row))) {
                if (v instanceof cc.Node) {
                    // if (v.active) {
                    //     v.active = false
                    if (this._isCellActive(v)) {
                        this._setCellActive(v, false);
                        if (this._delegate.cellWillHide) {
                            this._delegate.cellWillHide(this._view, rowTemp, col, v)
                        }
                        // --如果需要复用则加入队列
                        if (this._isReuse) {
                            let key = v["cellType"]
                            if (!key) {
                                key = "def" 
                            }
                            let queue = this._recycleQueue[key] || []
                            queue.push(v)
                            delete this._createdCellMap[Number(i)] 
                            this._recycleQueue[key] = queue
                        }
                    } else if (!this._isCreateAll) {
                        // --清除队列中已经超出显示区的加载任务
                        for (let j = 0; j < this._waitCreateQueue.length; j++) {
                            let info = this._waitCreateQueue[j];
                            let index = info[1] * this._cellNumSize.width + info[2]
                            if (index === Number(i)) {
                                this._waitCreateQueue.splice(j, 1)
                                delete this._createdCellMap[index]
                                break
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * 查看cell当前显示状态
     * @param cell 
     * @returns cell当前是否显示
     */
    private _isCellActive(cell: cc.Node): boolean {
        return cell.active;
    }

    /**
     * 设置cell 显示隐藏
     * @param cell 
     * @param isActive 是否显示
     */
    private _setCellActive(cell: cc.Node, isActive: boolean) {
        cell.active = isActive;
    }
}
